// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/series_renderer_properties.dart';
import 'package:charts_grasp/src/chart/common/marker.dart';
import 'package:charts_grasp/src/chart/utils/enum.dart';
import 'package:charts_grasp/src/common/event_args.dart';
import 'package:charts_grasp/src/common/utils/typedef.dart';
import 'package:flutter/material.dart';

abstract class XyDataSeries<T, D> extends CartesianSeries<T, D> {
  XyDataSeries({
    super.key,
    super.onCreateRenderer,
    super.onRendererCreated,
    super.onPointTap,
    super.onPointDoubleTap,
    super.onPointLongPress,
    super.onCreateShader,
    ChartValueMapper<T, D>? xValueMapper,
    ChartValueMapper<T, dynamic>? yValueMapper,
    ChartValueMapper<T, String>? dataLabelMapper,
    super.name,
    required super.dataSource,
    super.xAxisName,
    super.yAxisName,
    ChartValueMapper<T, Color>? pointColorMapper,
    super.legendItemText,
    ChartValueMapper<T, dynamic>? sortFieldValueMapper,
    super.gradient,
    super.borderGradient,
    ChartValueMapper<T, num>? sizeValueMapper,
    ChartValueMapper<T, num>? highValueMapper,
    ChartValueMapper<T, num>? lowValueMapper,
    ChartValueMapper<T, bool>? intermediateSumPredicate,
    ChartValueMapper<T, bool>? totalSumPredicate,
    super.trendlines,
    super.width,
    super.markerSettings,
    super.isVisible,
    super.enableTooltip,
    super.emptyPointSettings,
    super.dataLabelSettings,
    super.animationDuration,
    super.dashArray,
    super.borderColor,
    super.borderWidth,
    super.selectionBehavior,
    super.isVisibleInLegend,
    super.legendIconType,
    super.opacity,
    super.animationDelay,
    super.color,
    super.initialSelectedDataIndexes,
    super.sortingOrder,
  }) : super(
            xValueMapper: xValueMapper != null ? (index) => xValueMapper(dataSource[index], index) : null,
            yValueMapper: yValueMapper != null ? (index) => yValueMapper(dataSource[index], index) : null,
            sortFieldValueMapper:
                sortFieldValueMapper != null ? (index) => sortFieldValueMapper(dataSource[index], index) : null,
            pointColorMapper:
                pointColorMapper != null ? (index) => pointColorMapper(dataSource[index], index) : null,
            dataLabelMapper: dataLabelMapper != null ? (index) => dataLabelMapper(dataSource[index], index) : null,
            sizeValueMapper: sizeValueMapper != null ? (index) => sizeValueMapper(dataSource[index], index) : null,
            highValueMapper: highValueMapper != null ? (index) => highValueMapper(dataSource[index], index) : null,
            lowValueMapper: lowValueMapper != null ? (index) => lowValueMapper(dataSource[index], index) : null,
            intermediateSumPredicate: intermediateSumPredicate != null
                ? (index) => intermediateSumPredicate(dataSource[index], index)
                : null,
            totalSumPredicate:
                totalSumPredicate != null ? (index) => totalSumPredicate(dataSource[index], index) : null,);
}

class CartesianChartPoint<D> {
  CartesianChartPoint(
      [this.x,
      this.y,
      this.dataLabelMapper,
      this.pointColorMapper,
      this.bubbleSize,
      this.high,
      this.low,
      this.open,
      this.close,
      this.volume,
      this.sortValue,
      this.minimum,
      this.maximum,
      this.isIntermediateSum,
      this.isTotalSum,
      this.maxYValue = 0,
      this.outliers,
      this.upperQuartile,
      this.lowerQuartile,
      this.mean,
      this.median,
      this.originValue,
      this.endValue,]) {
    x = x;
    y = y;
    sortValue = sortValue;
    markerPoint = markerPoint;
    isEmpty = isEmpty;
    isGap = isGap;
    isVisible = isVisible;
    bubbleSize = bubbleSize;
    pointColorMapper = pointColorMapper;
    dataLabelMapper = dataLabelMapper;
    high = high;
    low = low;
    open = open;
    close = close;
    markerPoint2 = markerPoint2;
    volume = volume;
    minimum = minimum;
    maximum = maximum;
    outliers = outliers;
    upperQuartile = upperQuartile;
    lowerQuartile = lowerQuartile;
    mean = mean;
    median = median;
    isIntermediateSum = isIntermediateSum;
    isTotalSum = isTotalSum;
    originValue = originValue;
    endValue = endValue;
    maxYValue = maxYValue;
  }

  D? x;

  D? y;

  D? xValue;

  D? yValue;

  D? sortValue;

  D? high;

  D? low;

  D? open;

  D? close;

  num? volume;

  ChartLocation? markerPoint;

  ChartLocation? markerPoint2;

  num? bubbleSize;

  bool? isEmpty;

  bool isGap = false;

  bool isDrop = false;

  bool _isMarkerEventTriggered = false;

  MarkerDetails? _markerDetails;

  bool isVisible = true;

  Color? pointColorMapper;

  String? dataLabelMapper;

  Rect? region;

  Rect? boxRectRegion;

  List<Rect>? outlierRegion;

  List<dynamic>? outlierRegionPosition;

  num? minimum;

  num? maximum;

  List<num>? outliers = <num>[];

  double? upperQuartile;

  double? lowerQuartile;

  num? mean;

  num? median;

  bool? isIntermediateSum;

  bool? isTotalSum;

  num? endValue;

  num? originValue;

  num maxYValue;

  bool labelRenderEvent = false;

  bool isTooltipRenderEvent = false;

  ChartLocation? openPoint;
  ChartLocation? closePoint;
  ChartLocation? centerOpenPoint;
  ChartLocation? centerClosePoint;
  ChartLocation? lowPoint;
  ChartLocation? highPoint;
  ChartLocation? centerLowPoint;
  ChartLocation? centerHighPoint;
  ChartLocation? currentPoint;
  ChartLocation? startControl;
  ChartLocation? endControl;
  ChartLocation? highStartControl;
  ChartLocation? highEndControl;
  ChartLocation? lowStartControl;
  ChartLocation? lowEndControl;
  ChartLocation? minimumPoint;
  ChartLocation? maximumPoint;
  ChartLocation? lowerQuartilePoint;
  ChartLocation? upperQuartilePoint;
  ChartLocation? centerMinimumPoint;
  ChartLocation? centerMaximumPoint;
  ChartLocation? medianPoint;
  ChartLocation? centerMedianPoint;
  ChartLocation? centerMeanPoint;
  ChartLocation? originValueLeftPoint;
  ChartLocation? originValueRightPoint;
  ChartLocation? endValueLeftPoint;
  ChartLocation? endValueRightPoint;
  ChartLocation? horizontalPositiveErrorPoint;
  ChartLocation? horizontalNegativeErrorPoint;
  ChartLocation? verticalPositiveErrorPoint;
  ChartLocation? verticalNegativeErrorPoint;

  ErrorBarValues? errorBarValues;

  List<ChartLocation> outliersPoint = <ChartLocation>[];

  List<Offset>? controlPoint;

  List<Offset>? controlPointshigh;

  List<Offset>? controlPointslow;

  List<Rect>? regions;

  double? cumulativeValue;

  Rect? trackerRectRegion;

  String? label;

  String? label2;

  String? label3;

  String? label4;

  String? label5;

  List<String> outliersLabel = <String>[];

  RRect? labelFillRect;

  RRect? labelFillRect2;

  RRect? labelFillRect3;

  RRect? labelFillRect4;

  RRect? labelFillRect5;

  List<RRect> outliersFillRect = <RRect>[];

  ChartLocation? labelLocation;

  ChartLocation? labelLocation2;

  ChartLocation? labelLocation3;

  ChartLocation? labelLocation4;

  ChartLocation? labelLocation5;

  List<ChartLocation> outliersLocation = <ChartLocation>[];

  bool dataLabelSaturationRegionInside = false;

  Rect? dataLabelRegion;

  Rect? dataLabelRegion2;

  Rect? dataLabelRegion3;

  Rect? dataLabelRegion4;

  Rect? dataLabelRegion5;

  List<Rect> outliersDataLabelRegion = <Rect>[];

  int? index;

  int? overallDataPointIndex;

  List<String>? regionData;

  int? visiblePointIndex;

  TextStyle? _dataLabelTextStyle;

  Color? _dataLabelColor;

  bool _isCustomTextColor = false;
}

class CartesianPointHelper {
  const CartesianPointHelper._();

  static TextStyle? getDataLabelTextStyle(CartesianChartPoint<dynamic> point) {
    return point._dataLabelTextStyle;
  }

  static void setDataLabelTextStyle(CartesianChartPoint<dynamic> point, TextStyle? style) {
    point._dataLabelTextStyle = style;
  }

  static Color? getDataLabelColor(CartesianChartPoint<dynamic> point) {
    return point._dataLabelColor;
  }

  static void setDataLabelColor(CartesianChartPoint<dynamic> point, Color? color) {
    point._dataLabelColor = color;
  }

  static bool getCustomTextColor(CartesianChartPoint<dynamic> point) {
    return point._isCustomTextColor;
  }

  static void setCustomTextColor(CartesianChartPoint<dynamic> point, bool isCustomTextColor) {
    point._isCustomTextColor = isCustomTextColor;
  }

  static bool getIsMarkerEventTriggered(CartesianChartPoint<dynamic> point) {
    return point._isMarkerEventTriggered;
  }

  static void setIsMarkerEventTriggered(CartesianChartPoint<dynamic> point, bool isMarkerEventTriggered) {
    point._isMarkerEventTriggered = isMarkerEventTriggered;
  }

  static MarkerDetails? getMarkerDetails(CartesianChartPoint<dynamic> point) {
    return point._markerDetails;
  }

  static void setMarkerDetails(CartesianChartPoint<dynamic> point, MarkerDetails? details) {
    point._markerDetails = details;
  }
}

class ChartLocation {
  ChartLocation(this.x, this.y);

  double x;

  double y;
}

abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer {
  @override
  void calculateEmptyPointValue(int pointIndex, CartesianChartPoint<dynamic> currentPoint,
      [CartesianSeriesRenderer? seriesRenderer,]) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer!);
    final int pointLength = seriesRendererDetails.dataPoints.length - 1;
    final String seriesType = seriesRendererDetails.seriesType;
    final CartesianChartPoint<dynamic> prevPoint = seriesRendererDetails
        .dataPoints[seriesRendererDetails.dataPoints.length >= 2 == true ? pointLength - 1 : pointLength];
    if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType == 'candle'
        ? seriesType == 'hiloopenclose' || seriesType == 'candle'
            ? (currentPoint.low == null ||
                currentPoint.high == null ||
                currentPoint.open == null ||
                currentPoint.close == null)
            : (currentPoint.low == null || currentPoint.high == null)
        : currentPoint.y == null) {
      switch (seriesRendererDetails.series.emptyPointSettings.mode) {
        case EmptyPointMode.zero:
          currentPoint.isEmpty = true;
          if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle')) {
            currentPoint.high = 0;
            currentPoint.low = 0;
            if (seriesType == 'hiloopenclose' || seriesType == 'candle') {
              currentPoint.open = 0;
              currentPoint.close = 0;
            }
          } else {
            currentPoint.y = 0;
          }
          break;

        case EmptyPointMode.average:
          if (seriesRenderer is XyDataSeriesRenderer) {
            seriesRendererDetails.calculateAverageModeValue(pointIndex, pointLength, currentPoint, prevPoint);
          }
          currentPoint.isEmpty = true;
          break;

        case EmptyPointMode.gap:
          if (seriesType == 'scatter' ||
              seriesType == 'column' ||
              seriesType == 'bar' ||
              seriesType == 'bubble' ||
              seriesType == 'splinearea' ||
              seriesType == 'rangecolumn' ||
              seriesType.contains('hilo') ||
              seriesType.contains('candle') ||
              seriesType == 'rangearea' ||
              seriesType.contains('stacked')) {
            currentPoint.y =
                pointIndex != 0 && (!seriesType.contains('stackedcolumn') && !seriesType.contains('stackedbar'))
                    ? prevPoint.y ?? 0
                    : 0;
            currentPoint.open = currentPoint.open ?? 0;
            currentPoint.close = currentPoint.close ?? 0;
            currentPoint.isVisible = false;
          } else if (seriesType.contains('line') || seriesType == 'area' || seriesType == 'steparea') {
            if (seriesType == 'splinerangearea') {
              currentPoint.low = currentPoint.low ?? (pointIndex != 0 ? prevPoint.low ?? 0 : 0);
              currentPoint.high = currentPoint.high ?? (pointIndex != 0 ? prevPoint.high ?? 0 : 0);
            } else {
              currentPoint.y = pointIndex != 0 ? prevPoint.y ?? 0 : 0;
            }
          }
          currentPoint.isVisible = false;
          currentPoint.isGap = true;
          break;
        case EmptyPointMode.drop:
          if (seriesType == 'splinerangearea') {
            currentPoint.low = currentPoint.low ?? (pointIndex != 0 ? prevPoint.low ?? 0 : 0);
            currentPoint.high = currentPoint.high ?? (pointIndex != 0 ? prevPoint.high ?? 0 : 0);
          }
          currentPoint.y = pointIndex != 0 &&
                  (seriesType != 'area' &&
                      seriesType != 'splinearea' &&
                      seriesType != 'splinerangearea' &&
                      seriesType != 'steparea' &&
                      !seriesType.contains('stackedcolumn') &&
                      !seriesType.contains('stackedbar'))
              ? prevPoint.y ?? 0
              : 0;
          currentPoint.isDrop = true;
          currentPoint.isVisible = false;
          break;
        default:
          currentPoint.y = 0;
          break;
      }
    }
  }
}
